//!
//! # Ledger, world state
//!

pub mod staking;

use crate::{
    cfg::DaemonCfg as Cfg,
    common::{
        handle_bloom, hash_to_evm_format, tm_proposer_to_evm_format, BlockHeight,
        HashValue, HashValueEvmFormat, HashValueRef, TmAddress, TmAddressRef,
    },
    ethvm::{self, tx::GAS_PRICE_MIN},
    tx::Tx,
};
use ethereum::{util::trie_root, Log as EthLog};
use ethereum_types::{Bloom, H160, H256, U256};
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use ruc::*;
use serde::{Deserialize, Serialize};
use std::{
    collections::{hash_map::Entry, BTreeMap, HashMap, VecDeque},
    fs,
    io::ErrorKind,
    mem,
    sync::Arc,
};
use vsdb::{
    hash, BranchName, Mapx, MapxOrd, OrphanVs, ParentBranchName, ValueDe, ValueEn, Vecx,
    VersionName, Vs, VsMgmt,
};

// used by web3 APIs
pub const MAIN_BRANCH_NAME: BranchName = BranchName(b"Main");
const DELIVER_TX_BRANCH_NAME: BranchName = BranchName(b"DeliverTx");
const CHECK_TX_BRANCH_NAME: BranchName = BranchName(b"CheckTx");

static LEDGER_SNAPSHOT_PATH: Lazy<String> = Lazy::new(|| {
    let dir = format!("{}/microverus/ledger", vsdb::vsdb_get_custom_dir());
    pnk!(fs::create_dir_all(&dir));
    dir + "/ledger.json"
});

// Used by web3 APIs
pub static RECEIPT_CACHE: Lazy<RwLock<ReceiptCache>> =
    Lazy::new(|| RwLock::new(ReceiptCache::default()));

#[derive(Clone, Debug)]
pub struct Ledger {
    pub main: Arc<RwLock<StateBranch>>,
    pub deliver_tx: Arc<RwLock<StateBranch>>,
    pub check_tx: Arc<RwLock<StateBranch>>,

    // used by web3 APIs
    pub api_state: State,

    // how many versions at least shoult be always availiable
    state_version_cap: usize,
}

impl Ledger {
    pub fn new(cfg: &Cfg) -> Result<Self> {
        let mut state = State::new(&cfg.tendermint_home_dir).c(d!())?;

        state
            .branch_create(
                MAIN_BRANCH_NAME,
                VsVersion::default().encode_value().as_ref().into(),
                false,
            )
            .c(d!())?;
        state.branch_set_default(MAIN_BRANCH_NAME).c(d!())?;

        state.chain_id.set_value(cfg.chain_id).c(d!())?;
        state.chain_name.set_value_ref(&cfg.chain_name).c(d!())?;
        state
            .chain_version
            .set_value_ref(&cfg.chain_version)
            .c(d!())?;

        state
            .evm
            .gas_price
            .set_value(cfg.gas_price.map(|v| v.into()).unwrap_or(*GAS_PRICE_MIN))
            .c(d!())?;
        state
            .evm
            .block_gas_limit
            .set_value(cfg.block_gas_limit.unwrap_or(u128::MAX).into())
            .c(d!())?;
        state
            .evm
            .block_base_fee_per_gas
            .set_value(cfg.block_base_fee_per_gas.unwrap_or_default().into())
            .c(d!())?;

        let main = StateBranch::new(&state, MAIN_BRANCH_NAME).c(d!())?;

        unsafe {
            state
                .branch_create_without_new_version(DELIVER_TX_BRANCH_NAME, false)
                .c(d!())?;
            state
                .branch_create_without_new_version(CHECK_TX_BRANCH_NAME, false)
                .c(d!())?;
        }

        let deliver_tx = StateBranch::new(&state, DELIVER_TX_BRANCH_NAME).c(d!())?;
        let check_tx = StateBranch::new(&state, CHECK_TX_BRANCH_NAME).c(d!())?;

        Ok(Self {
            main: Arc::new(RwLock::new(main)),
            deliver_tx: Arc::new(RwLock::new(deliver_tx)),
            check_tx: Arc::new(RwLock::new(check_tx)),
            api_state: state,
            state_version_cap: cfg.vsdb_prune_cap,
        })
    }

    #[inline(always)]
    pub fn consensus_refresh(&self, proposer: TmAddress, timestamp: u64) -> Result<()> {
        self.refresh_inner(proposer, timestamp, false).c(d!())
    }

    #[inline(always)]
    fn loading_refresh(&self) -> Result<()> {
        self.refresh_inner(Default::default(), Default::default(), true)
            .c(d!())
    }

    fn refresh_inner(
        &self,
        proposer: TmAddress,
        timestamp: u64,
        is_loading: bool,
    ) -> Result<()> {
        let mut main = self.main.write();

        if is_loading {
            return main.clean_up().c(d!());
        }

        let mut deliver_tx = self.deliver_tx.write();
        let mut check_tx = self.check_tx.write();

        main.prepare_next_block(proposer.clone(), timestamp)
            .c(d!())
            .map(|_| main.update_evm_aux())?;
        deliver_tx
            .prepare_next_block(proposer.clone(), timestamp)
            .c(d!())?;
        check_tx.prepare_next_block(proposer, timestamp).c(d!())?;

        let h = main.block_in_process.header.height;
        let ver = VsVersion::new_with_default_mark(h, 0);
        main.state
            .version_create(ver.encode_value().as_ref().into())
            .c(d!(h))?;

        unsafe {
            main.state
                .branch_create_by_base_branch_without_new_version(
                    DELIVER_TX_BRANCH_NAME,
                    ParentBranchName::from(MAIN_BRANCH_NAME.0),
                    true,
                )
                .c(d!())
                .and_then(|_| {
                    main.state
                        .branch_create_by_base_branch_without_new_version(
                            CHECK_TX_BRANCH_NAME,
                            ParentBranchName::from(MAIN_BRANCH_NAME.0),
                            true,
                        )
                        .c(d!())
                })?;
        }

        deliver_tx
            .state
            .branch_set_default(DELIVER_TX_BRANCH_NAME)
            .c(d!())
            .and_then(|_| {
                check_tx
                    .state
                    .branch_set_default(CHECK_TX_BRANCH_NAME)
                    .c(d!())
            })
    }

    // NOTE:
    // - Only triggered by the 'main' branch of the `Ledger`
    #[inline(always)]
    pub fn commit(&self) -> Result<HashValue> {
        let mut main = self.main.write();
        let mut deliver_tx = self.deliver_tx.write();

        mem::swap(&mut main.block_in_process, &mut deliver_tx.block_in_process);
        mem::swap(
            &mut main.tx_hashes_in_process,
            &mut deliver_tx.tx_hashes_in_process,
        );

        let height = main.block_in_process.header.height;

        let base_ver = VsVersion::new_with_default_mark(height, 0);

        unsafe {
            deliver_tx
                .state
                .version_rebase(base_ver.encode_value().as_ref().into())
                .c(d!())?;
        }

        main.state
            .branch_merge_to(DELIVER_TX_BRANCH_NAME, MAIN_BRANCH_NAME)
            .c(d!())?;

        main.commit().c(d!())?;

        // prune outdated versions per 100 blocks
        if 77 == height % 100 {
            println!(
                "[ PRUNE ] height: {}, total versions before prune: {}",
                height,
                main.state.version_list_globally().len()
            );
            pnk!(main.state.prune(Some(self.state_version_cap)));
            println!(
                "[ PRUNE ] height: {}, total versions after prune: {}",
                height,
                main.state.version_list_globally().len()
            );
        }

        Ok(main.last_block_hash())
    }

    #[inline(always)]
    pub fn load_from_snapshot(cfg: &Cfg) -> Result<Option<Self>> {
        match StateBranch::load_from_snapshot().c(d!()) {
            Ok(Some(main)) => {
                let mut deliver_tx = main.clone();
                deliver_tx.branch = DELIVER_TX_BRANCH_NAME.0.to_owned();

                let mut check_tx = main.clone();
                check_tx.branch = CHECK_TX_BRANCH_NAME.0.to_owned();

                let api_state = main.state.clone();

                let ledger = Ledger {
                    main: Arc::new(RwLock::new(main)),
                    deliver_tx: Arc::new(RwLock::new(deliver_tx)),
                    check_tx: Arc::new(RwLock::new(check_tx)),
                    api_state,
                    state_version_cap: cfg.vsdb_prune_cap,
                };

                ledger.loading_refresh().c(d!())?;

                Ok(Some(ledger))
            }
            Ok(None) => Ok(None),
            Err(e) => Err(e).c(d!()),
        }
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StateBranch {
    pub state: State,
    pub branch: Vec<u8>,

    block_in_process: Block,
    tx_hashes_in_process: Vec<HashValue>,
}

impl StateBranch {
    #[inline(always)]
    pub fn new(state: &State, branch: BranchName) -> Result<Self> {
        let mut s = state.clone();
        s.branch_set_default(branch).c(d!())?;

        Ok(Self {
            state: s,
            branch: branch.0.to_owned(),
            tx_hashes_in_process: vec![],
            block_in_process: Block::default(),
        })
    }

    #[inline(always)]
    fn prepare_next_block(&mut self, proposer: TmAddress, timestamp: u64) -> Result<()> {
        self.tx_hashes_in_process.clear();

        let (h, prev_hash) = self
            .last_block()
            .map(|b| (b.header.height, b.header_hash))
            .unwrap_or_default();
        self.block_in_process = Block::new(1 + h, proposer, timestamp, prev_hash);

        Ok(())
    }

    #[inline(always)]
    fn clean_up(&self) -> Result<()> {
        let last_h = self.last_block_height();
        let ver = VsVersion::new_with_default_mark(last_h, 0).encode_value();

        pd!(format!("Truncate to height: {}", last_h));

        self.state
            .branch_keep_only(&[MAIN_BRANCH_NAME])
            .c(d!())
            .and_then(|_| {
                self.state
                    .branch_truncate_to(
                        self.branch.as_slice().into(),
                        ver.as_ref().into(),
                    )
                    .c(d!())
            })
            .and_then(|_| self.state.version_clean_up_globally().c(d!()))
    }

    // Deal with each transaction.
    // Will be used by all the 3 branches of `Ledger`.
    pub fn apply_tx(&mut self, tx: Tx) -> Result<()> {
        let ver = VsVersion::new_with_random_mark(
            self.block_in_process.header.height,
            1 + self.tx_hashes_in_process.len() as u64,
        )
        .encode_value();

        self.state.version_create(ver.as_ref().into()).c(d!())?;

        let tx_hash = tx.hash();
        match tx.clone() {
            Tx::Evm(evm_tx) => match evm_tx.apply(self, false) {
                Ok((ret, mut receipt)) => {
                    self.charge_fee(ret.caller, ret.fee_used);
                    self.tx_hashes_in_process.push(tx_hash.clone());
                    self.block_in_process.txs.push(tx);

                    let tx_index = self.tx_hashes_in_process.len() as u64 - 1;
                    let mut logs = ret.gen_logs(&tx_hash, tx_index);
                    receipt.add_logs(logs.as_mut_slice());
                    receipt.tx_hash = tx_hash.clone();
                    receipt.tx_index = tx_index;
                    self.block_in_process
                        .header
                        .receipts
                        .insert(tx_hash, receipt);
                }
                Err((ret, msg)) => {
                    pnk!(self.state.version_pop());
                    if let Some(ret) = ret.as_ref() {
                        self.charge_fee(ret.caller, ret.fee_used);
                    }
                    RECEIPT_CACHE.write().insert(
                        hash_to_evm_format(&tx_hash),
                        BlockHeight::MAX,
                        Default::default(),
                        Default::default(),
                    );
                    return Err(eg!(ret.map(|ret| ret.to_string()).unwrap_or(msg)));
                }
            },
            Tx::Native(native_tx) => {
                let caller = native_tx.caller;
                native_tx
                    .apply(self)
                    .map(|_fee| {
                        self.tx_hashes_in_process.push(tx_hash);
                        self.block_in_process.txs.push(tx);
                    })
                    .map_err(|e| {
                        pnk!(self.state.version_pop());
                        self.charge_fee(caller, self.state.staking.static_fee().into());
                        eg!(e)
                    })?
            }
        };

        Ok(())
    }

    // NOTE:
    // - Only triggered by the 'main' branch of the `Ledger`
    fn commit(&mut self) -> Result<()> {
        // Make it never empty,
        // thus the root hash will always exist
        self.tx_hashes_in_process.push(hash(&[&[]]).to_vec());

        let hashes = self
            .tx_hashes_in_process
            .iter()
            .map(|h| (h, [0u8; 0]))
            .collect::<Vec<_>>();
        self.block_in_process.header.tx_trie_root =
            trie_root(hashes).as_bytes().to_vec();

        // Calculate the total amount of block gas to be used
        let mut block_gas_used: U256 = U256::zero();
        let mut log_idx_in_block = 0;
        self.block_in_process
            .header
            .receipts
            .iter()
            .for_each(|(_, r)| {
                block_gas_used += r.tx_gas_used;
            });
        let mut b = Bloom::from_slice(self.block_in_process.bloom.as_slice());
        for hash in self.tx_hashes_in_process.iter() {
            if let Some(mut r) = self.block_in_process.header.receipts.get_mut(hash) {
                r.block_gas_used = block_gas_used;

                // Populate the log_idx_in_block field in the log
                for log in r.logs.iter_mut() {
                    log.log_index_in_block = log_idx_in_block;
                    log_idx_in_block += 1;
                }

                handle_bloom(&mut b, r.logs.as_slice());
            }
        }

        self.block_in_process.bloom = b.as_bytes().to_vec();
        self.block_in_process.header_hash = self.block_in_process.header.hash();

        let block = mem::take(&mut self.block_in_process);

        let evm_block_hash = hash_to_evm_format(&block.header_hash);

        {
            let mut rc = RECEIPT_CACHE.write();
            for (tx_hash, receipt) in block.header.receipts.iter() {
                rc.insert(
                    hash_to_evm_format(tx_hash),
                    block.header.height,
                    evm_block_hash,
                    receipt.clone(),
                );
            }
        }

        self.state
            .evm
            .block_hashes
            .insert(block.header.height, evm_block_hash);

        self.state
            .evm
            .block_hash_index
            .insert(evm_block_hash.as_bytes().to_vec(), block.header.height);

        for (index, tx) in block.txs.iter().enumerate() {
            self.state
                .tx_hash_index
                .insert(hash_to_evm_format(&tx.hash()), (block.header.height, index));
        }

        self.state.blocks.insert(block.header.height, block);

        vsdb::vsdb_flush();
        self.write_snapshot().c(d!())
    }

    #[inline(always)]
    fn update_evm_aux(&mut self) {
        let b = self.branch.as_slice();
        self.state.evm.update_vicinity(
            U256::from(self.state.chain_id.get_value_by_branch(b.into()).unwrap()),
            tm_proposer_to_evm_format(&self.block_in_process.header.proposer),
            U256::from(self.block_in_process.header.timestamp),
        );
    }

    // #[inline(always)]
    // pub fn get_evm_state(&mut self) -> &ethvm::State {
    //     &self.state.evm
    // }

    #[inline(always)]
    pub fn get_evm_state_mut(&mut self) -> &mut ethvm::State {
        &mut self.state.evm
    }

    #[inline(always)]
    fn charge_fee(&self, caller: H160, amount: U256) {
        alt!(amount.is_zero(), return);
        let mut account = pnk!(self.state.evm.token.accounts.get(&caller));
        account.balance = account.balance.saturating_sub(amount);
        pnk!(self.state.evm.token.accounts.insert(caller, account));
    }

    // #[inline(always)]
    // fn branch_name(&self) -> BranchName {
    //     self.branch.as_slice().into()
    // }

    #[inline(always)]
    pub fn last_block(&self) -> Option<Block> {
        self.state.blocks.last().map(|(_, b)| b)
    }

    #[inline(always)]
    fn last_block_height(&self) -> BlockHeight {
        self.state
            .blocks
            .last()
            .map(|(h, b)| {
                assert_eq!(h, b.header.height);
                h
            })
            .unwrap_or(0)
    }

    #[inline(always)]
    pub fn last_block_hash(&self) -> HashValue {
        self.last_block().unwrap_or_default().header_hash
    }

    #[inline(always)]
    fn load_from_snapshot() -> Result<Option<Self>> {
        match fs::read(&*LEDGER_SNAPSHOT_PATH) {
            Ok(c) => StateBranch::decode_value(c.as_slice()).c(d!()).map(Some),
            Err(e) => {
                if let ErrorKind::NotFound = e.kind() {
                    Ok(None)
                } else {
                    Err(e).c(d!())
                }
            }
        }
    }

    #[inline(always)]
    fn write_snapshot(&self) -> Result<()> {
        let contents = self.encode_value();
        fs::write(&*LEDGER_SNAPSHOT_PATH, &contents).c(d!())
    }
}

#[derive(Vs, Clone, Debug, Deserialize, Serialize)]
pub struct State {
    pub chain_id: OrphanVs<u64>,
    pub chain_name: OrphanVs<String>,
    pub chain_version: OrphanVs<String>,

    pub evm: ethvm::State,
    pub staking: staking::State,

    // maintained by the 'main' branch only
    pub blocks: MapxOrd<BlockHeight, Block>,
    pub tx_hash_index: Mapx<HashValueEvmFormat, (BlockHeight, usize)>,
}

impl State {
    #[inline(always)]
    fn new(tm_home: &str) -> Result<Self> {
        let mut s = Self::gen();
        s.version_create(VersionName(b"")).c(d!())?;

        tendermint_config::TendermintConfig::load_toml_file(
            &(tm_home.to_owned() + "/config/config.toml"),
        )
        .c(d!())?
        .load_genesis_file(tm_home)
        .c(d!(tm_home))
        .map(|g| {
            g.validators
                .iter()
                .map(|v| {
                    (
                        v.pub_key.to_bytes(),
                        1 + u64::from(v.power) / s.staking.score_max() as u64,
                    )
                })
                .collect::<BTreeMap<_, _>>()
        })
        .and_then(|validators| s.staking.set_initial_validators(validators).c(d!()))?;

        Ok(s)
    }

    #[inline(always)]
    fn gen() -> Self {
        Self {
            chain_id: OrphanVs::new(),
            chain_name: OrphanVs::new(),
            chain_version: OrphanVs::new(),
            evm: ethvm::State::new(),
            staking: staking::State::new(),
            blocks: MapxOrd::new(),
            tx_hash_index: Mapx::new(),
        }
    }
}

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Block {
    pub header: BlockHeader,
    pub header_hash: HashValue,
    // transaction vec
    pub txs: Vecx<Tx>,
    // bloom
    pub bloom: Vec<u8>,
}

impl Block {
    #[inline(always)]
    fn new(
        height: BlockHeight,
        proposer: TmAddress,
        timestamp: u64,
        prev_hash: HashValue,
    ) -> Self {
        let header = BlockHeader {
            height,
            proposer,
            timestamp,
            prev_hash,
            ..Default::default()
        };
        Self {
            header,
            txs: Vecx::new(),
            bloom: Bloom::default().as_bytes().to_vec(),
            ..Default::default()
        }
    }
}

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct BlockHeader {
    // height of the current block
    pub height: BlockHeight,
    // proposer of the current block
    pub proposer: TmAddress,
    // timestamp of the current block
    pub timestamp: u64,
    // root hash of the transaction trie of the current block
    pub tx_trie_root: Vec<u8>,
    // hash of the previous block header
    pub prev_hash: HashValue,
    // execution results for each transaction
    pub receipts: BTreeMap<HashValue, Receipt>,
}

impl BlockHeader {
    #[inline(always)]
    fn hash(&self) -> HashValue {
        #[derive(Serialize)]
        struct Contents<'a> {
            height: BlockHeight,
            proposer: TmAddressRef<'a>,
            timestamp: u64,
            tx_trie_root: HashValueRef<'a>,
            prev_hash: HashValueRef<'a>,
            receipts: &'a BTreeMap<HashValue, Receipt>,
        }

        let contents = Contents {
            height: self.height,
            proposer: &self.proposer,
            timestamp: self.timestamp,
            tx_trie_root: &self.tx_trie_root,
            prev_hash: &self.prev_hash,
            receipts: &self.receipts,
        }
        .encode_value();

        hash(&[&contents]).to_vec()
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct VsVersion {
    block_height: u64,
    // NOTE:
    // - starting from 1
    // - 0 is reserved for the block itself
    tx_position: u64,
    salt_mark: u64,
}

impl VsVersion {
    #[inline(always)]
    pub fn new(block_height: BlockHeight, tx_position: u64, salt_mark: u64) -> Self {
        Self {
            block_height,
            tx_position,
            salt_mark,
        }
    }

    #[inline(always)]
    pub fn new_with_default_mark(block_height: BlockHeight, tx_position: u64) -> Self {
        Self::new(block_height, tx_position, 0)
    }

    #[inline(always)]
    pub fn new_with_random_mark(block_height: BlockHeight, tx_position: u64) -> Self {
        Self::new(
            block_height,
            tx_position,
            rand::random::<u64>().saturating_add(1),
        )
    }
}

impl Default for VsVersion {
    fn default() -> Self {
        Self::new_with_default_mark(0, 0)
    }
}

pub struct ApplyResp {
    pub receipt: Option<Receipt>,
    pub logs: Option<Vec<Log>>,
}

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Receipt {
    // transaction hash
    pub tx_hash: HashValue,
    // transaction index in block
    pub tx_index: u64,
    // transaction originator
    pub from: Option<H160>,
    // transaction recipients
    pub to: Option<H160>,
    // the total amount of gas used for all transactions in this block
    pub block_gas_used: U256,
    // gas used for transaction
    pub tx_gas_used: U256,
    // here is contract address if recipients is none
    pub contract_addr: Option<H160>,
    // TODO: to be filled
    pub state_root: Option<HashValue>,
    // execute success or failure
    pub success: bool,
    // logs
    pub logs: Vec<Log>,
}

impl Receipt {
    #[inline(always)]
    pub fn add_logs(&mut self, logs: &mut [Log]) {
        for (index, log) in logs.iter_mut().enumerate() {
            log.tx_index = self.tx_index;
            log.log_index_in_tx = index as u64;
        }

        self.logs = logs.to_vec();
    }
}

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Log {
    // source address of this log
    pub address: H160,
    // 0 to 4 32 bytes of data for the index log parameter. In solidity, the first topic is event signatures
    pub topics: Vec<H256>,
    // One or more 32-byte un-indexed parameters containing this log
    pub data: Vec<u8>,
    // transaction hash
    pub tx_hash: HashValue,
    // transaction index in block
    pub tx_index: u64,
    // log index in block
    pub log_index_in_block: u64,
    // log index in transaction
    pub log_index_in_tx: u64,
    // returns true if the log has been deleted, false if it is a valid log
    pub removed: bool,
}

impl Log {
    #[inline(always)]
    pub fn new<'a>(
        log: &'a EthLog,
        tx_hash: HashValueRef<'a>,
        log_index_in_tx: usize,
        tx_index: u64,
    ) -> Self {
        Self {
            address: log.address,
            topics: log.topics.clone(),
            data: log.data.clone(),
            tx_hash: tx_hash.to_owned(),
            tx_index,
            log_index_in_block: 0,
            log_index_in_tx: log_index_in_tx as u64,
            removed: false,
        }
    }
}

#[derive(Default, Debug)]
pub struct ReceiptCache {
    h_list: VecDeque<HashValueEvmFormat>,
    h_map: HashMap<HashValueEvmFormat, (BlockHeight, HashValueEvmFormat, Receipt)>,
}

impl ReceiptCache {
    #[inline(always)]
    pub fn get(
        &self,
        tx_hash: &HashValueEvmFormat,
    ) -> Option<&(BlockHeight, HashValueEvmFormat, Receipt)> {
        self.h_map.get(tx_hash)
    }

    #[inline(always)]
    fn insert(
        &mut self,
        tx_hash: HashValueEvmFormat,
        height: BlockHeight,
        block_hash: HashValueEvmFormat,
        receipt: Receipt,
    ) {
        if 10_0000 < self.h_list.len() {
            for h in self.h_list.split_off(5_0000).iter() {
                self.h_map.remove(h);
            }
        }
        if let Entry::Vacant(not_found) = self.h_map.entry(tx_hash) {
            self.h_list.push_front(tx_hash);
            not_found.insert((height, block_hash, receipt));
        }
    }
}
